package im.composer.audioservers.rmi.comporessed;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.sound.sampled.AudioFormat;

import org.jaudiolibs.audioservers.AudioClient;
import org.jaudiolibs.audioservers.AudioConfiguration;
import org.tritonus.share.sampled.FloatSampleBuffer;

public class GZIPAudioTerminal extends UnicastRemoteObject implements CompressedAudioTerminal {

	/**
	 * 
	 */
	private static final long serialVersionUID = 7326432827663626985L;
	private transient final AudioClient client;
	private AudioConfiguration context;
	private int sampleSizeInBits = 24;

	public GZIPAudioTerminal(AudioClient client) throws RemoteException {
		this.client = client;
	}

	@Override
	public void configure(AudioConfiguration context) throws RemoteException {
		try {
			client.configure(context);
		} catch (Exception e) {
			throw new RemoteException(e.getLocalizedMessage(), e);
		}
		this.context = context;
	}

	public int getSampleSizeInBits() throws RemoteException {
		return sampleSizeInBits;
	}

	public void setSampleSizeInBits(int sampleSizeInBits) throws RemoteException {
		if (Arrays.asList(8, 16, 24, 32).contains(sampleSizeInBits)) {
			this.sampleSizeInBits = sampleSizeInBits;
		} else {
			throw new IllegalArgumentException();
		}
	}

	@Override
	public byte[] audioExchange(long time, byte[] inputs, int nframes) throws RemoteException {
		List<FloatBuffer> input;
		try {
			input = prepareInputBuffer(inputs, nframes);
		} catch (IOException e) {
			throw new RemoteException(e.getMessage(), e);
		}
		List<FloatBuffer> output = prepareOutputBuffer(nframes);
		client.process(time, input, output, nframes);
		byte[] arr = new byte[]{};
		try {
			arr = getOutputBytes(output);
		} catch (IOException e) {
			throw new RemoteException(e.getMessage(), e);
		}
		return arr;
	}

	private List<FloatBuffer> prepareInputBuffer(byte[] inputs, int nframes) throws IOException, RemoteException {
		List<FloatBuffer> input = new ArrayList<>(context.getInputChannelCount());
		ByteArrayInputStream bais = new ByteArrayInputStream(inputs);
		int inChannels = context.getInputChannelCount();
		FloatSampleBuffer ibuf = new FloatSampleBuffer(inChannels, context.getMaxBufferSize(), context.getSampleRate());
		AudioFormat format = new AudioFormat(context.getSampleRate(), sampleSizeInBits, inChannels, true, false);
		ByteArrayOutputStream baos = new ByteArrayOutputStream(ibuf.getByteArrayBufferSize(format));
		GZIPInputStream gis = new GZIPInputStream(bais);
		byte[] b = new byte[256];
		int n;
		while ((n = gis.read(b)) >= 0) {
			baos.write(b, 0, n);
		}
		gis.close();
		bais.close();
		ibuf.setSamplesFromBytes(baos.toByteArray(), 0, format, 0, nframes);
		for (int i = 0; i < inChannels; i++) {
			input.add(FloatBuffer.wrap(ibuf.getChannel(i)));
		}
		return input;
	}

	private List<FloatBuffer> prepareOutputBuffer(int nframes) throws RemoteException {
		int outChannels = context.getOutputChannelCount();
		List<FloatBuffer> output = new ArrayList<FloatBuffer>(outChannels);
		for (int i = 0; i < outChannels; i++) {
			FloatBuffer bufz = FloatBuffer.allocate(nframes);
			output.add(bufz);
		}
		return output;
	}

	private byte[] getOutputBytes(List<FloatBuffer> output) throws IOException, RemoteException {
		int outChannels = context.getOutputChannelCount();
		FloatSampleBuffer obuf = new FloatSampleBuffer(outChannels, context.getMaxBufferSize(), context.getSampleRate());
		for (int i = 0; i < outChannels; i++) {
			obuf.setRawChannel(i, output.get(i).array());
		}

		AudioFormat format = new AudioFormat(context.getSampleRate(), sampleSizeInBits, outChannels, true, false);
		ByteArrayOutputStream baos = new ByteArrayOutputStream(obuf.getByteArrayBufferSize(format));
		GZIPOutputStream gos = new GZIPOutputStream(baos);
		byte[] b = obuf.convertToByteArray(format);
		gos.write(b);
		gos.flush();
		gos.close();
		baos.flush();
		baos.close();
		byte[] arr = baos.toByteArray();
		return arr;
	}
}
